package webtrans

import (
	"context"
	"crypto/tls"
	"fmt"
	"gitee.com/DreamPublic/signal/pkg/proto"
	"gitee.com/DreamPublic/signal/utils"
	"github.com/lucas-clemente/quic-go"
	"go.uber.org/zap"
	"sync"
)

type Signal struct {
	connectCbs []proto.SignalCallback
	closeCbs   []proto.SignalCallback
	authCb     proto.AuthCallback
	cbLock     sync.Mutex
	// sessionId - *Session
	sessionMap sync.Map
	certFile   string
	keyFile    string
	port       int
}

func NewSignal(port int, certFile, keyFile string) *Signal {
	return &Signal{
		connectCbs: make([]proto.SignalCallback, 0),
		closeCbs:   make([]proto.SignalCallback, 0),
		authCb:     nil,
		cbLock:     sync.Mutex{},
		sessionMap: sync.Map{},
		certFile:   certFile,
		keyFile:    keyFile,
		port:       port,
	}
}

func (s *Signal) OnConnect(cb proto.SignalCallback) {
	s.cbLock.Lock()
	defer s.cbLock.Unlock()
	s.connectCbs = append(s.connectCbs, cb)
}

func (s *Signal) OnAuth(cb proto.AuthCallback) {
	s.cbLock.Lock()
	defer s.cbLock.Unlock()
	s.authCb = cb
}

func (s *Signal) OnClose(cb proto.SignalCallback) {
	s.cbLock.Lock()
	defer s.cbLock.Unlock()
	s.closeCbs = append(s.closeCbs, cb)
}

func (s *Signal) fireConnectCb(session *Session) {
	for _, cb := range s.connectCbs {
		cb(session)
	}
}

func (s *Signal) fireCloseCb(session *Session) {
	s.sessionMap.Delete(session.id)
	for _, cb := range s.closeCbs {
		go cb(session)
	}
}

func (s *Signal) fireAuthCb(info proto.AuthCbStruct) bool {
	if nil == s.authCb {
		return true
	} else {
		return s.authCb(info)
	}
}

func (s *Signal) Run() error {
	quicConfig := quic.Config{
		Versions:        []quic.VersionNumber{quic.Version1},
		KeepAlive:       true,
		EnableDatagrams: true,
	}
	keyPair, _ := tls.LoadX509KeyPair(s.certFile, s.keyFile)
	tlsConfig := tls.Config{
		Certificates: []tls.Certificate{keyPair},
		NextProtos:   []string{"h3"},
	}
	quicListen, _ := quic.ListenAddr(fmt.Sprintf(":%d", s.port), &tlsConfig, &quicConfig)
	log := zap.L()
	for {
		quicSession, acceptErr := quicListen.Accept(context.Background())
		if nil == acceptErr {
			// 连接成功,生成sessionId,触发连接回调,放入sessionMap
			sessionId := utils.UUID()
			log := log.With(zap.String("sessionId", sessionId))
			session := NewSession(sessionId, quicSession, s)
			s.sessionMap.Store(sessionId, session)
			log.Info("accept session success")
			// 处理后续webtrans逻辑
			go session.HandleWebTrans()
		} else {
			log.Error("accept session error", zap.Error(acceptErr))
		}
	}
}
